<?php
require 'SinaObject.class.php';
require 'SinaRequest.class.php';

/**
 *Sina接口运行时发生错误时，抛出此异常.
 */
class SinaRuntimeExecption extends Exception{
	function __construct($errorCode,$errorMessage){
		parent::__construct($errorMessage,$errorCode);
	}
}

/**
 * Sina接口运行时.
 */
class SinaRuntime {
	/**
	 * @var SinaRequest $request 用于访问sina接口.
	 */
	var $request;
	/**
	 * @var integer $maxTryCount 调用可重调用数据不变接口函数失败最大重试次数，默认2次
	 */
	var $maxTryCount=2;
	/**
	 * @var integer $minTrySpanMicroseconds 调用可重调用数据不变接口函数失败最小重试时间间隔，毫秒数，默认300毫秒。
	 */
	var $minTrySpanMicroseconds=300;
	/**
	 * @var integer $maxTrySpanMicroseconds 调用可可重调用数据不变接口函数失败最大重试时间间隔，毫秒数，默认500毫秒。
	 */
	var $maxTrySpanMicroseconds=500;	
	
	/**
	 * 创建sina接口对象
	 * @param array $sina_token 操作用户的sina令牌.
	 * <br/>必须包含以下参数：<br/>
	 * string $Username 商户在新浪商城的merchentId<br/>
	 * string $Password 新浪商城提供给商户的密码<br/>
	 * 还必须在新浪商城有Ip – 商户指定的有效ip组(有效ip之外的ip无权访问商城webservice)
	 */
	function __construct($sina_token) { //
		if (! is_array ( $sina_token ))
			throw new SinaRuntimeExecption ( - 1, 'SinaRuntime创建失败：参数类型错误' );
		
		$Username = $sina_token ['Username'];
		$Password = $sina_token ['Password'];
		
		if (! ($Username && $Password ))
			throw new SinaRuntimeExecption ( - 1, 'SinaRuntime创建失败：参数错误' );
		
		$this->request = new SinaRequest ();
		$this->request->app_token = array ('Username' => $Username, 
		            'Password' => $Password);
	}
	function __destruct() {
	   $this->request=NULL;
	}
	/**
	 * xml字符串解析为数组，xml字符串如<orders><order orderid="97913707" merchantid="6619" 
	 * paysum="32.90" isdeleted="0" ispaid="0" status="1" merchantorderid="" /></orders>
	 * <br/>如果不是xml字符串，不解析。<br/>原如果解析失败，抛出SinaRuntimeExecption异常
	 * @param string $xmlString xml字符串
	 * @param array $resultarr 解析后的结果数组
	 */
	function parseXmlString($xmlString,array &$resultarr){
		$xmlString=trim($xmlString);
		if(strpos($xmlString,'<')!==0) return ; //simple test is xml
		try{
		  $root =new SimpleXMLElement($xmlString);
		  $this->__fillArray($root,$resultarr);
		}catch (Exception $e){
			throw new SinaRuntimeExecption ( - 2, '新浪接口运行失败，返回xml解析错误:' . $e->getMessage());
		}
	}	
	/**
	 * 对象属性数组解析填充到对象属性。
	 * @param object $obj 要填充对象
	 * @param array $arr  对象属性数组
	 * @param string $complexClass 复杂属性的类名map数组
	 * @return object 填充后的对象
	 */
	function parseProperty($obj,array &$arr,$complexClass=NULL){
		if(! $obj || !$arr || count($arr)==0) return $obj;
		$refclass = new ReflectionObject ($obj);
		$this->__fillProperty($refclass,$obj,$arr,$complexClass);
		return $obj;
	}	
	/**
	 * 获得订单信息（只包含订单的一些基本信息），sina funcname: GetOrderInfo
	 * <br/>sina的格式为XML字符串，目的是为了保持扩展性和兼容性
	 * @param SinaOrder $result   要填充的订单信息对象，仅当$assoc===false时有效
	 * @param string $orderId     订单号
	 * @param boolean $returnobj      是否返回对象，默认为true。
	 * $returnobj：NULL 函数返回xml字符串；false 返回数组；true 返回SinaOrder对象
	 * @return string|array|SinaOrder 返回订单信息
	 */
	function GetOrderInfo(SinaOrder $result,$orderId,$returnobj=true){
		if(!$orderId && ($returnobj===true && ! is_object($result)))
		  throw  new SinaRuntimeExecption(-1,'GetOrderInfo运行失败：参数错误');
		$paramarr=array('orderId'=>$orderId);
		$resultval=$this->__fireRequest('GetOrderInfo',$paramarr);
		if(is_null($returnobj)) return $resultval;
		$resultarr=array();
		$this->parseXmlString($resultval,$resultarr);
		if($returnobj===false) return $resultarr;
		return $this->parseProperty($result,$resultarr,SinaOrder::$_DataLayerout['varclass']);
	}	
	/**
	 * 获得某日某商户的订单列表，sina funcname: GetOrderListByDate
	 * <br/>recdate：为订单生成日期，格式要求为yyyy-MM-dd，如2008-01-01，如果没有指定日期，则默认为当天
	 * <br/>sina默认返回的格式为XML字符串
	 * @param array $result      要填充的订单列表对象数组，类型@see SinaOrderListItem，仅当$assoc===false时有效
	 * @param string $rsdate     订单生成日期，格式要求为yyyy-MM-dd，如2008-01-01，默认为当天
	 * @param boolean $returnobj      是否返回对象，默认为true。
	 * $returnobj：NULL 函数返回xml字符串；false 返回订单项数组的数组，数组的key为order；
	 * true 返回SinaOrderListItem对象数组
	 * @return string|array      返回订单列表，类型@see SinaOrderListItem
	 */
	function &GetOrderListByDate(array &$result,$rsdate=NULL,$returnobj=true){
		if(!$rsdate &&  ! is_array($result))
		  throw  new SinaRuntimeExecption(-1,'GetOrderListByDate运行失败：参数错误');
		$paramarr=array('rsdate'=>$rsdate);
		$resultval=$this->__fireRequest('GetOrderListByDate',$paramarr);
		if(is_null($returnobj)) return $resultval;
		$resultarr=array();
		$this->parseXmlString($resultval,$resultarr);
		if($returnobj===false) return $resultarr;
		$list=new _SinaOrderList();
		$this->parseProperty($list,$resultarr,_SinaOrderList::$_DataLayerout['varclass']);
		$result=$list->order;
		return $result;
	}	
	/**
	 * 根据商户商品id查找对应的新浪商品id，sina funcname: GetSinaProductIdByMerchantProductId
	 * <br/>查询不到返回-1 merchantProductId-商户商品id 
	 * @param string $merchantProductId   商户商品id
	 * @return integer  新浪商品id，查询不到返回-1
	 */
	function GetSinaProductIdByMerchantProductId($merchantProductId){
		if(!$merchantProductId)
		  throw  new SinaRuntimeExecption(-1,'GetSinaProductIdByMerchantProductId运行失败：参数错误');
		$paramarr=array('merchantProductId'=>$merchantProductId);
		return $this->__fireRequest('GetSinaProductIdByMerchantProductId',$paramarr);
	}	
	/**
	 * 由商户发起的订单推送接口，sina funcname: TransferOrder
	 * <br/>适用于商户在获取订单列表后，如果发现某些订单不存在或者不一致，可以发起重新同步
	 * @param int $orderId  订单号
	 * @return string  
	 */
	function TransferOrder($orderId){
		if(!$orderId)
		  throw  new SinaRuntimeExecption(-1,'TransferOrder运行失败：参数错误');
		$paramarr=array('orderId'=>$orderId);
		return $this->__fireRequest('TransferOrder',$paramarr);
	}
	/**
	 * 商品基本数据维护，sina funcname: SaveProductInfo
	 * <br/>接收并保存商户产品XML数据，以商户的产品编码为判据，存在则更新，不存在则添加
	 * <br/>注意：<b>可SinaProduct对象的toXmlString函数得到xmlstring</b>
	 * @param string $xmlstring  xmlstring-传入的商品信息xml格式字符串  
	 * SinaProduct对象的toXmlString函数可得到xmlstring
	 * @return integer 成功：返回商城商品ID,身份验证失败：1,数据格式错误：8
	 */
	function SaveProductInfo($xmlstring){
		if(!$xmlstring)
		  throw  new SinaRuntimeExecption(-1,'SaveProductInfo运行失败：参数错误');
		$paramarr=array('xmlstring'=>$xmlstring);
		return $this->__fireRequest('SaveProductInfo',$paramarr,false);
	}
		
	//新浪函数返回值=>0：操作正确完成，1：身份验证失败，2：状态不能转换，3：SoapHeader为空，4：订单不符条件
	//5：订单不是支付状态,6：订单不是未支付状态,7: 设置的数值非法,8：更新失败,9：不是商户自己的订单或商品
	
	/**
	 * 存储订单信息，sina funcname: SaveOrderInfo
	 * <br/>目前仅提供给需要此服务的用户
	 * @param string $orderInfoXml  订单xml字符串
	 * @return integer  
	 */
	function SaveOrderInfo($orderInfoXml){
		if(!$orderInfoXml)
		  throw  new SinaRuntimeExecption(-1,'SaveOrderInfo运行失败：参数错误');
		$paramarr=array('orderInfoXml'=>$orderInfoXml);
		return $this->__fireRequest('SaveOrderInfo',$paramarr,false);
	}	

	/**
	 * 作废订单，sina funcname: SetCancel
	 * <br/>要求订单状态为0或者1，且支付状态为0，且只能是安全支付订单
	 * @param string $orderId  订单号
	 * @return integer  
	 */
	function SetCancel($orderId){
		if(!$orderId)
		  throw  new SinaRuntimeExecption(-1,'SetCancel运行失败：参数错误');
		$paramarr=array('orderId'=>$orderId);
		return $this->__fireRequest('SetCancel',$paramarr);
	}	
	/**
	 * 确认订单（已读），sina funcname: SetConfirm
	 * @param string $orderId  订单号
	 * @param integer $status  1 已确认
	 * @return integer  
	 */
	function SetConfirm($orderId,$status=1){
		if(!$orderId)
		  throw  new SinaRuntimeExecption(-1,'SetConfirm运行失败：参数错误');
		$paramarr=array('orderId'=>$orderId,'status'=>$status);
		return $this->__fireRequest('SetConfirm',$paramarr);
	}		
	/**
	 * 确认已发货，sina funcname: SetConsign
	 * @param string $orderId  订单号
	 * @return integer  
	 */
	function SetConsign($orderId){
		if(!$orderId)
		  throw  new SinaRuntimeExecption(-1,'SetConsign运行失败：参数错误');
		$paramarr=array('orderId'=>$orderId);
		return $this->__fireRequest('SetConsign',$paramarr);
	}	
	/**
	 * 修改支付状态为已支付，sina funcname: SetIsPaid
	 * <br/>仅限部分商户使用
	 * @param string $orderId  订单号
	 * @param integer $isPaid  1 已支付
	 * @return integer  
	 */
	function SetIsPaid($orderId,$isPaid=1){
		if(!$orderId)
		  throw  new SinaRuntimeExecption(-1,'SetIsPaid运行失败：参数错误');
		$paramarr=array('orderId'=>$orderId,'isPaid'=>$isPaid);
		return $this->__fireRequest('SetIsPaid',$paramarr);
	}	
	/**
	 * 设置退款，sina funcname: SetRefund
	 * @param string $orderId  订单号
	 * @return integer  
	 */
	function SetRefund($orderId){
		if(!$orderId)
		  throw  new SinaRuntimeExecption(-1,'SetRefund运行失败：参数错误');
		$paramarr=array('orderId'=>$orderId);
		return $this->__fireRequest('SetRefund',$paramarr);
	}		
	/**
	 * 根据商品ID设置商品上下架状态， sina funcname: 
	 * SetIsSellingOfProductByMerchantProductId |SetIsSellingOfProductBySinaProductId
	 * <br/>如果$isMerchantProductId为true，调用SetIsSellingOfProductByMerchantProductId，
	 * 否则调用SetIsSellingOfProductBySinaProductId
	 * @param string|int $id  商品ID， 如果$isMerchantProductId为true，
	 * 则为merchantProductId-商户商品id， 否则为sinaProductId-新浪商品id
	 * @param integer $IsSelling  商品上下架状态，0代表下架，1代表上架
	 * @param boolean $isMerchantProductId 是否商户商品ID
	 * @return integer  
	 */
	function SetIsSellingOfProduct($id,$IsSelling,$isMerchantProductId=true){
		if(!$id || !is_int($IsSelling))
		  throw  new SinaRuntimeExecption(-1,'SetIsSellingOfProduct运行失败：参数错误');
		$paramarr=array();
		if($isMerchantProductId) $paramarr['merchantProductId']=$id;
		else $paramarr['sinaProductId']=$id;
		$paramarr['IsSelling']=$IsSelling;
		if($isMerchantProductId) 
		  return $this->__fireRequest('SetIsSellingOfProductByMerchantProductId',$paramarr);
		else 
		  return $this->__fireRequest('SetIsSellingOfProductBySinaProductId',$paramarr);
	}	
	/**
	 * 根据商品ID修改商品销售价格， sina funcname: 
	 * SetMerchantPriceOfProductByMerchantProductId |SetMerchantPriceOfProductBySinaProductId
	 * <br/>如果$isMerchantProductId为true，调用SetMerchantPriceOfProductByMerchantProductId，
	 * 否则调用SetMerchantPriceOfProductBySinaProductId
	 * @param string|int $id  商品ID， 如果$isMerchantProductId为true，
	 * 则为merchantProductId-商户商品id， 否则为sinaProductId-新浪商品id
	 * @param double $merchantPrice   销售价格，大于等于0的浮点数   
	 * @param boolean $isMerchantProductId 是否商户商品ID
	 * @return integer  
	 */
	function SetMerchantPriceOfProduct($id,$merchantPrice,$isMerchantProductId=true){
		if(!$id || $merchantPrice<0)
		  throw  new SinaRuntimeExecption(-1,'SetMerchantPriceOfProduct运行失败：参数错误');
		$paramarr=array();
		if($isMerchantProductId) $paramarr['merchantProductId']=$id;
		else $paramarr['sinaProductId']=$id;
		$paramarr['merchantPrice']=$merchantPrice;
		if($isMerchantProductId) 
		  return $this->__fireRequest('SetMerchantPriceOfProductByMerchantProductId',$paramarr);
		else 
		  return $this->__fireRequest('SetMerchantPriceOfProductBySinaProductId',$paramarr);
	}	
	/**
	 * 通过商品ID设置商品库存， sina funcname: 
	 * SetStockOfProductByMerchantProductId |SetStockOfProductBySinaProductId
	 * <br/>如果$isMerchantProductId为true，调用SetStockOfProductByMerchantProductId，
	 * 否则调用SetStockOfProductBySinaProductId
	 * @param string|int $id  商品ID， 如果$isMerchantProductId为true，
	 * 则为merchantProductId-商户商品id， 否则为sinaProductId-新浪商品id
	 * @param integer $stock   库存量--大于等于0的整数，如果设置为0，可视为将商品下架 
	 * @param boolean $isMerchantProductId 是否商户商品ID
	 * @return integer  
	 */
	function SetStockOfProduct($id,$stock,$isMerchantProductId=true){
		if(!$id || $stock<0)
		  throw  new SinaRuntimeExecption(-1,'SetStockOfProduct运行失败：参数错误');
		$paramarr=array();
		if($isMerchantProductId) $paramarr['merchantProductId']=$id;
		else $paramarr['sinaProductId']=$id;
		$paramarr['stock']=$stock;
		if($isMerchantProductId) 
		  return $this->__fireRequest('SetStockOfProductByMerchantProductId',$paramarr);
		else 
		  return $this->__fireRequest('SetStockOfProductBySinaProductId',$paramarr);
	}	
	/**
	 * 根据商品ID修改商品URL， sina funcname: 
	 * SetUrlOfProductByMerchantProductId |SetUrlOfProductBySinaProductId
	 * <br/>如果$isMerchantProductId为true，调用SetUrlOfProductByMerchantProductId，
	 * 否则调用SetUrlOfProductBySinaProductId
	 * @param string|int $id  商品ID， 如果$isMerchantProductId为true，
	 * 则为merchantProductId-商户商品id， 否则为sinaProductId-新浪商品id
	 * @param string $productUrl   商品URL
	 * @param boolean $isMerchantProductId 是否商户商品ID
	 * @return integer   
	 */
	function SetUrlOfProduct($id,$productUrl,$isMerchantProductId=true){
		if(!$id || !$productUrl)
		  throw  new SinaRuntimeExecption(-1,'SetUrlOfProduct运行失败：参数错误');
		$paramarr=array();
		if($isMerchantProductId) $paramarr['merchantProductId']=$id;
		else $paramarr['sinaProductId']=$id;
		$paramarr['productUrl']=$productUrl;
		if($isMerchantProductId) 
		  return $this->__fireRequest('SetUrlOfProductByMerchantProductId',$paramarr);
		else 
		  return $this->__fireRequest('SetUrlOfProductBySinaProductId',$paramarr);
	}	
	/**
	 * 商品图片更新(通过商户商品ID)， sina funcname: UpdateImageOfProductByMerchantProductId
	 * @param string $id  商户商品id
	 * @param string $imageurl   商品主图的全路径url
	 * @param boolean $isMerchantProductId 不使用
	 * @return integer   
	 */
	function UpdateImageOfProduct($id,$imageurl,$isMerchantProductId=true){
		if(!$id || !$imageurl)
		  throw  new SinaRuntimeExecption(-1,'UpdateImageOfProduct运行失败：参数错误');
		$paramarr=array();
		$paramarr['merchantProductId']=$id;
		$paramarr['imageurl']=$imageurl;
		return $this->__fireRequest('UpdateImageOfProductByMerchantProductId',$paramarr);
	}	

	
	
	//----private function ------//
	function __fillArray($root,array &$resultarr){
		$rootarr=get_object_vars($root);
		foreach ($rootarr as $key=>$val) {
			if($key=='@attributes'){
			  foreach ($val as $k1=>$v1)
				 $resultarr[$k1]=$v1;				
			}
			else if(is_object($val)){
				$newarr=array();
				$this->__fillArray($val,$newarr);
				$resultarr[$key]=$newarr;
			}
			else if(is_array($val) && count($val)>0){//is array
				$valarr=array();
				for($i=0;$i<count($val);$i++){
					$newarr=array();
					$this->__fillArray($val[$i],$newarr);
					$valarr[]=$newarr;
				}
				$resultarr[$key]=$valarr;
			}
			else
			 $resultarr[$key]=$val;
		}		
	}
    private function __fillProperty($refclass,$obj,array &$arr,$complexClass=NULL){
		foreach ( $arr as $key => $val ) {
			if(! $key) continue;
			if ($refclass->hasProperty ( $key )===true) {
				$prop = $refclass->getProperty ( $key );
				if(! is_array($val)){ //simple property
                    if($prop->isPrivate() || $prop->isProtected()){
                       $prop->setAccessible(true);
                    }
					$prop->setValue ( $obj, $val );
				}
				elseif(count($val)>0){
				   $propvalue=$prop->getValue($obj);
				   if(is_array($propvalue)){
				   	  $classname=$complexClass[$key];
				   	  if(! $classname)
				   	     $prop->setValue($obj,$val);   //simple set array
				   	   else{
				   	   	  $comprefclass = new ReflectionClass($classname);
				   	   	  $valarr=array();
				   	   	  $isleaf=true;
				   	   	  foreach($val as $valitem){//see is obj array
				   	   	  	if(is_array($valitem)){
				   	   	  		$isleaf=false;
				   	   	  		break;
				   	   	  	}
				   	   	  }
				   	   	  if($isleaf){ //single object
				   	   	  	$compobj=$comprefclass->newInstance();
				   	   	  	$this->__fillProperty($comprefclass,$compobj,$val,$complexClass);
				   	   	  	$valarr[]=$compobj;
				   	   	  }
				   	   	  else{   //object array
				   	   	    foreach($val as $valitem){
				   	   	      $compobj=$comprefclass->newInstance();
				   	   	      $this->__fillProperty($comprefclass,$compobj,$valitem,$complexClass);
				   	   	      $valarr[]=$compobj;
				   	   	    }
				   	   	  }
				   	   	  $prop->setValue($obj,$valarr); //set obj array
				   	   }
				   }else{
				   	  $classname=$complexClass[$key];
				   	  if($classname || ($propvalue && is_object($propvalue))){
				   	  	$compobj=NULL;
				   	  	if($propvalue){
				   	  	  $classname=get_class($propvalue);
				   	  	  $compobj=$propvalue;
				   	  	}
				   	    $comprefclass = new ReflectionClass($classname);
				   	    if(! $compobj) $compobj=$comprefclass->newInstance();
			            $this->__fillProperty($comprefclass,$compobj,$val,$complexClass);
			            $prop->setValue($obj,$compobj); //set obj
				   	  }else{
				   	    if($prop->isPrivate() || $prop->isProtected()){
                           $prop->setAccessible(true);
                        }				   	  	
				   	  	$prop->setValue($obj,$val);   //simple set array
				   	  }
				   }
				}
			}else{  //not found this property,add property
				  $obj->$key=$val;
			}
		}
		return $obj;		
	} 			
	private function __fireRequest($funcname,array $paramArr,$retry=true,SinaRequest $tmprequset=NULL){
		if($tmprequset) $request=$tmprequset;
		else  $request=$this->request;
		$tryCount=$this->maxTryCount;
		if ($retry) { //仅可对可 重调用数据不变接口函数 使用
			do {
				try {
					$arr = $request->get($funcname, $paramArr );
					if (is_null($arr) && !is_null($request->soap_fault) &&
					    $request->soap_fault !='error:验证错误' && $request->soap_fault !='error:没有找到任何订单') //验证错误
						$tryCount --;
					else {
						$tryCount = - 1;
						break;
					}
				} catch ( Exception $e ) {
					$tryCount --;
				}
				if ($tryCount < $this->maxTryCount) {
					$ms = rand ( $this->minTrySpanMicroseconds, $this->maxTrySpanMicroseconds );
					usleep ( $ms );
				}
			} while ( $tryCount > 1 && $tryCount < $this->maxTryCount );
		}//仅可对可 重调用数据不变接口函数 使用
		if ($tryCount!=-1 || !$retry) {
			try {
				$arr = $request->get( $funcname, $paramArr );
			} catch ( Exception $e ) {
				throw new SinaRuntimeExecption ( - 2, '新浪接口运行失败:' . $e->getMessage () );
			}
		
		}
		if (is_null($arr) && !is_null($request->soap_fault)){
			if(is_string($request->soap_fault))
			  throw new SinaRuntimeExecption ( - 2, '新浪接口运行失败:' . $request->soap_fault );
			else if($request->soap_fault instanceof  SoapFault)
			   throw new SinaRuntimeExecption ( - 2, '新浪接口运行失败:' . $request->soap_fault->faultstring);
			else 
			   throw new SinaRuntimeExecption ( - 2, '新浪接口运行失败');
		}	
		else if(is_int($arr) && (1<=$arr && $arr<=9)){
			switch ($arr){
				case 1: $errinfo='身份验证失败'; break;
				case 2: $errinfo='状态不能转换'; break;
				case 3: $errinfo='SoapHeader为空'; break;
				case 4: $errinfo='订单不符条件'; break;
				case 5: $errinfo='订单不是支付状态'; break;
				case 6: $errinfo='订单不是未支付状态'; break;
				case 7: $errinfo='设置的数值非法'; break;
				case 8: $errinfo='更新失败'; break;
				case 9: $errinfo='不是商户自己的订单或商品'; break;
		  }
		  throw new SinaRuntimeExecption ( $arr, '新浪接口运行失败：'.$errinfo);
		}		
		return $arr;
	}

}

